﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using StyleMVVM.Logging;

namespace StyleMVVM.View
{
	public class ViewModelHandlerExtension : MarkupExtension
	{
		private static string supplemental = typeof(ViewModelHandlerExtension).FullName;

		public ViewModelHandlerExtension()
		{
		}

		public ViewModelHandlerExtension(string method)
		{
			Method = method;
		}

		[ConstructorArgument("method")]
		public string Method { get; set; }

		public override object ProvideValue(IServiceProvider serviceProvider)
		{
			IProvideValueTarget targetProvider = serviceProvider
				                                     .GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

			if (targetProvider == null)
			{
				throw new InvalidOperationException("Cannot find IProvideValueTarget service");
			}

			FrameworkElement target = targetProvider.TargetObject as FrameworkElement;

			if (target == null)
			{
				throw new InvalidOperationException("This extension can only be used on FrameworkElement classes");
			}

			Type delegateType = null;

			EventInfo eventInfo = targetProvider.TargetProperty as EventInfo;

			if (eventInfo != null)
			{
				delegateType = eventInfo.EventHandlerType;
			}
			else
			{
				MethodInfo targetEventAddMethod = targetProvider.TargetProperty as MethodInfo;

				if (targetEventAddMethod != null)
				{
					ParameterInfo[] pars = targetEventAddMethod.GetParameters();
					delegateType = pars[1].ParameterType;
				}
			}

			if (delegateType != null)
			{
				//Retrieves the method info of the proxy handler
				MethodInfo methodInfo = this.GetType().GetMethod("DefaultEventHandler",
				                                                 BindingFlags.NonPublic | BindingFlags.Instance);

				//Create a delegate to the proxy handler on the markupExtension
				return Delegate.CreateDelegate(delegateType, this, methodInfo);
			}

			return null;
		}

		private void DefaultEventHandler(object sender, EventArgs eventArgs)
		{
			FrameworkElement frameworkElement = sender as FrameworkElement;

			if (frameworkElement != null)
			{
				ExecuteMethodOnDataContext(frameworkElement, sender, eventArgs);
			}
		}

		private void ExecuteMethodOnDataContext(FrameworkElement frameworkElement, object sender, EventArgs eventArgs)
		{
			MethodInfo methodCall = null;
			object currentDataContext = null;

			FrameworkElement tempFrameworkElement = frameworkElement;

			while (tempFrameworkElement != null && methodCall == null)
			{
				currentDataContext = tempFrameworkElement.DataContext;

				if (currentDataContext != null)
				{
					foreach (MethodInfo declaredMethod in currentDataContext.GetType().GetTypeInfo().GetDeclaredMethods(Method))
					{
						if (declaredMethod.GetParameters().Count() == 2)
						{
							methodCall = declaredMethod;
							break;
						}
					}
				}

				tempFrameworkElement = VisualTreeHelper.GetParent(tempFrameworkElement) as FrameworkElement;
			}

			if (methodCall != null)
			{
				try
				{
					methodCall.Invoke(currentDataContext, new[] { sender, eventArgs });
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while invoking viewmodel method to handle event", supplemental, exp);
				}
			}
		}
	}
}